package app.server.project.main.service.service;

import app.server.project.core.constants.Def;
import app.server.project.core.constants.TcpCmd;
import app.server.project.core.exception.SysError;
import app.server.project.core.http.CMSLstResp;
import app.server.project.core.util.AssertUtil;
import app.server.project.core.websocket.WSMgr;
import app.server.project.main.service.bo.event.*;
import app.server.project.main.service.constants.GlobalCfgKey;
import app.server.project.main.service.db.dao.cfg.GlobalCfgDao;
import app.server.project.main.service.db.dao.cfg.RaceCfgDao;
import app.server.project.main.service.db.dao.user.*;
import app.server.project.main.service.db.entity.user.UserInviteEntity;
import app.server.project.main.service.dto.AddInviteReq;
import app.server.project.main.service.dto.cms.QueryUserInfoReq;
import app.server.project.main.service.dto.invite.InviteBTCDto;
import app.server.project.main.service.dto.invite.InviteDto;
import app.server.project.main.service.dto.invite.InviteFriendReq;
import app.server.project.main.service.service.race.CoinType;
import app.server.project.main.service.service.race.DetailType;
import app.server.project.main.service.service.rank.RankType;
import app.server.project.main.service.service.recharge.CallbackType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
@Slf4j
public class InviteService {

    private final static long PageSize = 50;
    private final static long COMMON = 5000;
    private final static long VIP = 5 * 5000;
    private final static long CacheIndex = 1;
    private final static long PushTime = 2;
    private final static long BTCVal = 1000;
    private final static long BTC_CLICK = 5;

    private final UserInviteDao userInviteDao;
    private final GlobalCfgDao globalCfgDao;
    private final UserExtLock userExtLock;
    private final ApplicationEventPublisher publisher;
    private final LockService lockService;
    private final ResService resService;
    private final UserInfoService userInfoService;
    private final RaceCfgDao raceCfgDao;
    private final AccountDao accountDao;
    private final UserRaceDao userRaceDao;
    private final TaskExecutor taskExecutor;
    private final EnergyService energyService;
    private final UserActiveRewardDao userActiveRewardDao;


    @EventListener(ShareEvent.class)
    @Async(Def.CommonPool)
    public void invite(ShareEvent req) {
        if (req.getInviteUserId() == req.getBeInviteUserId()) return;
        if (req.getInviteUserId() == Def.Zero) return;
        if (req.getBeInviteUserId() == Def.Zero) return;

        var lock = this.lockService.getLock(req.getInviteUserId());
        //检查邀请者有效性
        synchronized (lock) {
            var realUser = this.accountDao.getOpenIdByUserid(req.getInviteUserId());
            if (realUser.getUserId() == Def.Zero) return;
        }


        var db = this.userInviteDao.getByIds(req.getInviteUserId(), req.getBeInviteUserId());
        db.setInviteUserId(req.getInviteUserId());
        db.setBeInviteUserId(req.getBeInviteUserId());
        db.setVipType(req.getIsPremium());
        db.setCreateTime(LocalDateTime.now());
        db.setSynDb(true);
        this.userInviteDao.flushInviteUserCache(db);
        this.publisher.publishEvent(InviteEvent.of(req.getInviteUserId()));


        var commonNum = COMMON;
        if (req.getIsPremium() == Def.True) {
            commonNum = VIP;
            if (this.globalCfgDao.has(GlobalCfgKey.INVITE_VIP)) {
                commonNum = this.globalCfgDao.getOneByKey(GlobalCfgKey.INVITE_VIP).getLong();
            }

        } else {
            if (this.globalCfgDao.has(GlobalCfgKey.INVITE_COMMON)) {
                commonNum = this.globalCfgDao.getOneByKey(GlobalCfgKey.INVITE_COMMON).getLong();
            }
        }

        synchronized (lock) {
            var data = this.userExtLock.getUserExtByUserExtId(req.getInviteUserId());
            //发送ppk币奖励
            data.addInviteNum(commonNum);
            //设置邀请人数
            data.addInviteUserNum();
            this.receiveEnergy(req.getInviteUserId());
            this.doCacheLst(req.getInviteUserId(), db);
            this.publisher.publishEvent(AddRaceValEvent.of(req.getInviteUserId(), this.raceCfgDao.getAiCoin(), commonNum, DetailType.Invite.getType()));
            var dto = this.userInfoService.getBaseInfo(req.getBeInviteUserId());
            WSMgr.push(req.getInviteUserId(), TcpCmd.Invite, dto);
        }
    }

    private void receiveEnergy(long userId) {
        var data = this.userExtLock.getUserExtByUserExtId(userId);
        AssertUtil.isTrue(data.getInviteNum() > Def.Zero, SysError.Repeat_Again_Draw_Energy);
        this.resService.addAICoin(userId, data.getInviteNum(), true);
        data.setInviteNum(Def.Zero);
    }

    /**
     * 处理缓存列表问题
     *
     * @param userId
     */
    private void doCacheLst(long userId, UserInviteEntity lastInviter) {
        var dbs = this.userInviteDao.getInviteFriendCache(userId, CacheIndex * PageSize);
        dbs.add(Def.Zero, lastInviter);
        if (CacheIndex * PageSize >= dbs.size()) {
            return;
        }
        var nonCaches = dbs.subList((int) (CacheIndex * PageSize), dbs.size());
        dbs.removeAll(nonCaches);
    }

    private List<UserInviteEntity> getCacheLst(long userId, InviteFriendReq req) {
        var cacheDB = this.userInviteDao.getInviteFriendCache(userId, CacheIndex * PageSize);
        if (cacheDB.isEmpty()) {
            return List.of();
        }
        var start = req.getPageIndex() * PageSize;
        var end = Math.min(start + PageSize, cacheDB.size());
        return cacheDB.subList((int) start, (int) end);
    }

    public InviteDto getInvite(long userId, InviteFriendReq req) {
        var data = this.userExtLock.getUserExtByUserExtId(userId);
        //缓存前3页数据
        List<UserInviteEntity> dbs = new ArrayList<>();
        if (CacheIndex > req.getPageIndex()) {
            var lock = this.lockService.getLock(userId);
            synchronized (lock) {
                dbs = this.getCacheLst(userId, req);
            }
        } else {
            //直接命中数据库
            dbs = this.userInviteDao.getInviteFriend(userId, req.getPageIndex() + 1, PageSize);
        }
        var friends = dbs.stream().map(v -> {
            return this.userInfoService.getInviteInfo(v.getBeInviteUserId(), v.getBtcType(), v.getVipType());
        }).collect(Collectors.toList());
        var commonNum = COMMON;
        if (this.globalCfgDao.has(GlobalCfgKey.INVITE_COMMON)) {
            commonNum = this.globalCfgDao.getOneByKey(GlobalCfgKey.INVITE_COMMON).getLong();
        }
        var vipNum = VIP;
        if (this.globalCfgDao.has(GlobalCfgKey.INVITE_VIP)) {
            vipNum = this.globalCfgDao.getOneByKey(GlobalCfgKey.INVITE_VIP).getLong();
        }
        var aiCoinCfg = this.raceCfgDao.getAiCoin();
        return InviteDto.of(friends, data.getInviteNum(), aiCoinCfg.getRate(), commonNum, vipNum, createBTCDto());
    }


    private InviteBTCDto createBTCDto() {
        //检查赠送比特币数量是否配置
        var btcVal = BTCVal;
        if (this.globalCfgDao.has(GlobalCfgKey.Invite_BTC)) {
            btcVal = this.globalCfgDao.getOneByKey(GlobalCfgKey.Invite_BTC).getLong();
        }
        var btcCfg = this.raceCfgDao.getOne(CoinType.BTC_COIN);
        return InviteBTCDto.of(btcVal, btcCfg.getRate());
    }

    public CMSLstResp<UserInviteEntity> getInviteFriend(QueryUserInfoReq req) {
        var db = this.userInviteDao.getBeInviteUsers(req.getTargetUserId(), req.getPageIndex(), req.getPageSize());
        return CMSLstResp.of(db.getRecords(), db.getTotal());
    }


    @EventListener(ClickEvent.class)
    public void onClickEvent(ClickEvent event) {
        try {
            if (event.getRaceId() != CoinType.BTC_COIN) return;
            var clickNum = BTC_CLICK;
            if (this.globalCfgDao.has(GlobalCfgKey.Click_Num_BTC)) {
                clickNum = this.globalCfgDao.getOneByKey(GlobalCfgKey.Click_Num_BTC).getLong();
            }
            var userRaces = this.userRaceDao.getRaceInfo(event.getUserId());
            var btcRace = this.userRaceDao.getOne(userRaces, CoinType.BTC_COIN, event.getUserId());
            btcRace.addClickNum();
            //开始检查比特币点击次数
            if (clickNum > btcRace.getClickNum()) {
                //不满足
                return;
            }

            //有效用户
            var inviteUser = this.userInviteDao.getInviteUser(event.getUserId());
            if (inviteUser.getId() == Def.Zero) return;
            if (inviteUser.getBtcType() == Def.True) return;
            inviteUser.setBtcType(Def.True);


            this.taskExecutor.execute(() -> {
                //检查赠送比特币数量是否配置
                var btcVal = BTCVal;
                if (this.globalCfgDao.has(GlobalCfgKey.Invite_BTC)) {
                    btcVal = this.globalCfgDao.getOneByKey(GlobalCfgKey.Invite_BTC).getLong();
                }
                //开始对邀请人发送比特币奖励
                var targetLock = this.lockService.getLock(inviteUser.getInviteUserId());
                //记录比特币收益
                this.publisher.publishEvent(AddRaceValEvent.
                        of(inviteUser.getInviteUserId(),
                                this.raceCfgDao.getOne(CoinType.BTC_COIN),
                                btcVal,
                                DetailType.Invite.getType()));
                synchronized (targetLock) {
                    var targetExt = this.userExtLock.getUserExtByUserExtId(inviteUser.getInviteUserId());
                    //添加比特币有效人数
                    targetExt.addBtcUserNum();
                    this.resService.addRaceNum(inviteUser.getInviteUserId(), CoinType.BTC_COIN, btcVal, true);
                    //设置比特币奖励生效
                    var cacheDB = this.userInviteDao.getInviteFriendCache(inviteUser.getInviteUserId(), CacheIndex * PageSize);
                    cacheDB.forEach(v -> {
                        if (v.getBtcType() == Def.True) return;
                        if (v.getBeInviteUserId() == event.getUserId()) {
                            v.setBtcType(Def.True);
                        }
                    });
                }
            });
        } catch (Exception e) {
            log.error("检查比特币有效用户出错", e);
        }
    }


    @EventListener(ClickEvent.class)
    public void doInviteRank(ClickEvent event) {
        //有效用户
        var inviteUser = this.userInviteDao.getInviteUser(event.getUserId());
        if (inviteUser.getId() == Def.Zero) return;
        if (inviteUser.getRankType() == Def.True) return;
        inviteUser.setRankType(Def.True);
        this.publisher.publishEvent(CommonRankEvent.of(RankType.InviteDay, inviteUser.getInviteUserId(), 1, true));
        this.publisher.publishEvent(CommonRankEvent.of(RankType.InviteSum, inviteUser.getInviteUserId(), 1, true));
        this.publisher.publishEvent(CommonRankEvent.of(RankType.InviteWeek, inviteUser.getInviteUserId(), 1, true));


        //第二重奖励
//        var energy = 900;
//        if (this.globalCfgDao.has(GlobalCfgKey.Invite_Active_Energy)) {
//            energy = this.globalCfgDao.getOneByKey(GlobalCfgKey.Invite_Active_Energy).getInt();
//        }
//        this.energyService.addEnergy(inviteUser.getInviteUserId(), energy);

        //第4重奖励
//        UserActiveRewardEntity userActiveRewardEntity = this.userActiveRewardDao.getInfoById(inviteUser.getInviteUserId());
//        userActiveRewardEntity.addInviteNum(1);
//        var inviteNum = 10;
//        if (this.globalCfgDao.has(GlobalCfgKey.Invite_Active_PPT_NUM)) {
//            inviteNum = this.globalCfgDao.getOneByKey(GlobalCfgKey.Invite_Active_PPT_NUM).getInt();
//        }
//        var index = userActiveRewardEntity.getInviteNum() / inviteNum;
//        if (index > Def.Zero) {
//            var rewardNum = userActiveRewardEntity.getShareFlag();
//            var reward = (index - rewardNum);
//            if (reward > Def.Zero) {
//                var ppt = 100000;
//                if (this.globalCfgDao.has(GlobalCfgKey.Invite_Active_PPT_VAL)) {
//                    ppt = this.globalCfgDao.getOneByKey(GlobalCfgKey.Invite_Active_PPT_VAL).getInt();
//                }
//                userActiveRewardEntity.addShareFlag((int) reward);
//                this.publisher.publishEvent(AddRaceValEvent.of(inviteUser.getInviteUserId(), this.raceCfgDao.getOne(CoinType.AI_COIN), reward * ppt, DetailType.RankLevel.getType()));
//                this.resService.addResNum(inviteUser.getInviteUserId(), CoinType.AI_COIN, reward * ppt, true);
//            }
//        }

    }

    //奖励体力
    @EventListener(RechargeSuccessEvent.class)
    public void onRewardEnergy(RechargeSuccessEvent event) {
        if (event.getCallbackType() != CallbackType.Energy) return;
        var inviteUser = this.userInviteDao.getInviteUser(event.getUserId());
        if (inviteUser.getId() == Def.Zero) return;
        if (inviteUser.getRankType() == Def.FALSE) return;
        var rate = 50;
        if (this.globalCfgDao.has(GlobalCfgKey.Invite_Active_Recharge_VAL)) {
            rate = this.globalCfgDao.getOneByKey(GlobalCfgKey.Invite_Active_Recharge_VAL).getInt();
        }
        this.energyService.addEnergy(inviteUser.getInviteUserId(), event.getParam() * rate / 100);
    }

    @EventListener(RechargeSuccessEvent.class)
    public void onRewardClick(RechargeSuccessEvent event) {
        if (event.getCallbackType() != CallbackType.Click) return;
        var inviteUser = this.userInviteDao.getInviteUser(event.getUserId());
        if (inviteUser.getId() == Def.Zero) return;
        if (inviteUser.getRankType() == Def.FALSE) return;
        var ext = this.userExtLock.getUserExtByUserExtId(inviteUser.getInviteUserId());
        var rate = 50;
        if (this.globalCfgDao.has(GlobalCfgKey.Invite_Active_Recharge_VAL)) {
            rate = this.globalCfgDao.getOneByKey(GlobalCfgKey.Invite_Active_Recharge_VAL).getInt();
        }
        ext.addPropClickNum(event.getParam() * rate / 100);
        WSMgr.push(inviteUser.getInviteUserId(), TcpCmd.PropClickNum, ext.getPropClickNum());
    }

    public void addInvite(AddInviteReq req) {
        this.publisher.publishEvent(CommonRankEvent.of(RankType.InviteDay, req.getUserId(), req.getNum(), true));
        this.publisher.publishEvent(CommonRankEvent.of(RankType.InviteSum, req.getUserId(), req.getNum(), true));
        this.publisher.publishEvent(CommonRankEvent.of(RankType.InviteWeek, req.getUserId(), req.getNum(), true));
    }

}
